#include "fs.h"
#include "klib.h"
#include "boot_info.h"
#include "cpu_instr.h"
#include "log.h"
#include "console.h"
#include <sys/file.h>
#include "task.h"
#include "file.h"
#include "dev.h"
#include "list.h"
#include "devfs.h"
#include "disk.h"
#include "fatfs.h"
#include "os_cfg.h"

#define FS_TABLE_SIZE 10

extern fs_op_t devfs_op;
extern fs_op_t fatfs_op;
static fs_t   *root_fs;

static list_t mounted_list;               // have been initialized file system
static fs_t   fs_table[FS_TABLE_SIZE];    // global file sys table, alloc to free_list
static list_t free_list;                  // when mount, init and add to mounted_list

#if 0
static uint8_t TEMP_ADDR[100*1024];
static uint8_t * temp_pos;
#define TEMP_FILE_ID 100
#endif

static int is_fd_bad(int file)
{
    if ((file < 0) || (file >= TASK_OFILE_NR))
    {
        return 1;
    }
    return 0;
}

static void read_disk(uint32_t sector, uint32_t count, uint8_t *buf)
{
    outb(0x1F6, 0xE0);
    outb(0x1F2, (uint8_t)(count >> 8));
    outb(0x1F3, (uint8_t)(sector >> 24));    // start sector
    outb(0x1F4, 0);
    outb(0x1F5, 0);

    outb(0x1F2, (uint8_t)count);
    outb(0x1F3, (uint8_t)(sector));
    outb(0x1F4, (uint8_t)(sector >> 8));
    outb(0x1F5, (uint8_t)(sector >> 16));

    outb(0x1F7, 0x24);
    uint16_t *data_buf = (uint16_t *)buf;
    while (count--)
    {
        while ((inb(0x1F7) & 0x88) != 0x8)
        {
        }
        for (int i = 0; i < SECTOR_SIZE / 2; i++)
        {
            *data_buf++ = inw(0x1F0);
        }
    }
}

void mount_list_init(void)
{
    list_init(&free_list);
    // all the item in fs_table add to free_list, waited to be alloc
    for (int i = 0; i < FS_TABLE_SIZE; i++)
    {
        list_insert_last(&free_list, &fs_table[i].node);
    }
    list_init(&mounted_list);
}
static fs_op_t *get_fs_op(fs_type_t type, int major)
{
    switch (type)
    {
    case FS_FAT16:
        return &fatfs_op;
    case FS_DEVFS:
        return &devfs_op;
    default:
        return (fs_op_t *)0;
    }
}

static fs_t *mount(fs_type_t type, char *mount_point, int dev_major, int dev_minor)
{
    fs_t *fs = (fs_t *)0;
    log_printf("fs:mount:mount file system,name:%s,dev:%x", mount_point, dev_major);
    list_node_t *curr = list_first(&mounted_list);
    while (curr)
    {
        fs = container_of(curr, fs_t, node);
        if (kernel_strncmp(fs->mount_point, mount_point, FS_MOUNTP_SIZE) == 0)
        {
            log_printf("fs already mounted:%x", mount_point);
            return fs;
            // goto mount_failed;
        }
        curr = curr->next;
    }
    list_node_t *free_node = list_remove_first(&free_list);
    if (!free_node)
    {
        log_printf("no free fs, mount failed");
        goto mount_failed;
    }
    fs          = container_of(free_node, fs_t, node);
    fs_op_t *op = get_fs_op(type, dev_major);
    if (!op)
    {
        log_printf("fs:mount:unsupported fs type:%d", type);
        goto mount_failed;
    }
    kernel_memset(fs, 0, sizeof(fs_t));
    kernel_strncpy(fs->mount_point, mount_point, FS_MOUNTP_SIZE);
    fs->op = op;
    if (op->mount(fs, dev_major, dev_minor) < 0)
    {
        log_printf("fs:mount:mount failed, mount point:%s", mount_point);
    }
    list_insert_last(&mounted_list, &fs->node);
    return fs;
mount_failed:
    if (fs)
    {
        list_insert_last(&free_list, &fs->node);
    }
    return (fs_t *)0;
}
void fs_init(void)
{
    mount_list_init();
    file_table_init();
    disk_init();
    fs_t *fs = mount(FS_DEVFS, "/dev", 0, 0);
    ASSERT(fs != (fs_t *)0);
    // root_fs=mount(FS_FAT16,"/home",ROOT_DEV);
    root_fs = mount(FS_FAT16, "/home", DEV_DISK, 0xb1);
    ASSERT(root_fs != (fs_t *)0);
}

static int is_path_valid(const char *path)
{
    if ((path == (char *)0) || (*path == '\0'))
    {
        return 0;
    }
    return 1;
}
static int path_begin_with(const char *path, const char *key)
{
    int key_len = kernel_strlen(key);
    if (kernel_strncmp(path, key, key_len) == 0)
    {
        return 1;
    }
    return 0;
}
static void fs_protect(fs_t *fs)
{
    if (fs->mutex)
    {
        mutex_lock(fs->mutex);
    }
}
static void fs_unprotect(fs_t *fs)
{
    if (fs->mutex)
    {
        mutex_unlock(fs->mutex);
    }
}

int sys_open(const char *name, int flags, ...)
{
#if 0
    if(kernel_strncmp(name,"/shell.elf",9)==0){
        kernel_memset((void*)TEMP_ADDR,0,sizeof(TEMP_ADDR));
        //read_disk(5000,80,TEMP_ADDR);
        int dev_id=dev_open(DEV_DISK,0xa0,(void*)0);
        dev_read(dev_id,5000,(uint8_t*)TEMP_ADDR,80);
        temp_pos=(uint8_t*)TEMP_ADDR;
        return TEMP_FILE_ID;
    }
#endif
    int     fd   = -1;
    file_t *file = (file_t *)0;
    // dev/tty
    // if(kernel_strncmp(name,"tty",3)==0){
    // if(kernel_strncmp(name,"/dev",4)==0){
    if (!is_path_valid(name))
    {
        log_printf("path is not valid");
        return -1;
    }
    file = file_alloc();
    if (file)
    {
        fd = task_alloc_fd(file);
        if (fd < 0)
        {
            goto sys_open_failed;
        }
    }
    else
    {
        goto sys_open_failed;
    }
    fs_t        *fs   = (fs_t *)0;
    list_node_t *node = list_first(&mounted_list);
    while (node)
    {
        fs_t *curr = container_of(node, fs_t, node);
        if (path_begin_with(name, curr->mount_point))
        {
            fs = curr;
            break;
        }
        node = node->next;
    }
    if (fs)
    {
        // get tty index
        name = path_next_child(name);
    }
    else
    {
        fs = root_fs;
    }

    /*int num=name[4]-'0';
    int dev_id=dev_open(DEV_TTY,num,0);
    if(dev_id<0){
        goto sys_open_failed;
    }
    file->dev_id=dev_id;*/
    // file->dev_id=-1;
    file->mode = flags;
    // file->pos=0;//rw start pos
    // file->ref=1;
    file->fs   = fs;
    file->type = FILE_TTY;
    kernel_strncpy(file->file_name, name, FILE_NAME_SIZE);
    // int err=devfs_op.open((fs_t*)0,name,file);
    fs_protect(fs);
    int err = fs->op->open(fs, name, file);
    if (err < 0)
    {
        fs_unprotect(fs);
        log_printf("fs:sys_open:devfs_op.open,open failed: %d", err);
        goto sys_open_failed;
    }
    fs_unprotect(fs);
    return fd;
sys_open_failed:
    file_free(file);
    if (fd >= 0)
    {
        task_release_fd(fd);
    }
    return -1;
}
int sys_read(int file, char *ptr, int len)
{
#if 0
    if(file==TEMP_FILE_ID){
        kernel_memcpy(ptr,temp_pos,len);
        temp_pos+=len;
        return len;
    }
#endif
    if (is_fd_bad(file) || !ptr || len <= 0)
    {
        return -1;
    }
    file_t *p_file = task_file(file);
    if (!p_file)
    {
        log_printf("sys_read: file not opened! task_name:%s, fd:%x", task_current_task()->name, file);
        return -1;
    }
    if (p_file->mode == O_WRONLY)
    {
        log_printf("file is write only, can't read");
        return -1;
    }

    // return dev_read(p_file->dev_id,0,ptr,len);
    fs_protect(p_file->fs);
    int err = p_file->fs->op->read(ptr, len, p_file);
    fs_unprotect(p_file->fs);
    return err;
}
int sys_write(int file, char *ptr, int len)
{
    if (is_fd_bad(file))
    {
        return -1;
    }
    file_t *p_file = task_file(file);
    if (!p_file)
    {
        log_printf("sys_write:file not opened! task_name:%s,fd:%d", task_current_task()->name, file);
        return -1;
    }
    if (p_file->mode == O_RDONLY)
    {
        log_printf("file is read only, can't write");
        return -1;
    }
    fs_protect(p_file->fs);
    int err = p_file->fs->op->write(ptr, len, p_file);
    fs_unprotect(p_file->fs);
    return err;
    // return dev_write(p_file->dev_id,0,ptr,len);
#if 0
    if(file==1){
        //ptr[len]='\0';
        console_write(0,ptr,len);
        //log_printf("%s",ptr);
    }
#endif
    return -1;
}
int sys_lseek(int file, int ptr, int dir)
{
#if 0
    if(file==TEMP_FILE_ID){
        temp_pos = (uint8_t*)(TEMP_ADDR+ptr);
        return 0;
    }
#endif
    if (is_fd_bad(file))
    {
        return -1;
    }
    file_t *p_file = task_file(file);
    if (!p_file)
    {
        log_printf("sys_lseek:file not opened! task_name:%s,fd:%d", task_current_task()->name, file);
        return -1;
    }
    fs_protect(p_file->fs);
    int err = p_file->fs->op->seek(p_file, ptr, dir);
    fs_unprotect(p_file->fs);
    return err;
}
int sys_close(int file)
{
#if 0
    if(file == TEMP_FILE_ID){
        return 0;
    }
#endif
    if (is_fd_bad(file))
    {
        return -1;
    }
    file_t *p_file = task_file(file);
    ASSERT(p_file->ref > 0);
    fs_t *fs = p_file->fs;
    if (p_file->ref-- == 1)
    {
        fs_protect(fs);
        fs->op->close(p_file);
        fs_unprotect(fs);
        file_free(p_file);
    }
    task_release_fd(file);
    return 0;
}
int sys_ioctl(int file, int cmd, int arg0, int arg1)
{
    if (is_fd_bad(file))
    {
        log_printf("file %d is not valid", file);
        return -1;
    }
    file_t *p_file = task_file(file);
    if (!p_file)
    {
        log_printf("file not opened");
        return -1;
    }
    ASSERT(p_file->ref > 0);
    fs_t *fs = p_file->fs;
    fs_protect(fs);
    int err = fs->op->ioctl(p_file, cmd, arg0, arg1);
    fs_unprotect(fs);
    return err;
}

int sys_isatty(int file)
{
    if (is_fd_bad(file))
    {
        return -1;
    }
    file_t *p_file = task_file(file);
    if (!p_file)
    {
        log_printf("sys_isatty: file not opended. %d", file);
        return -1;
    }
    return p_file->type == FILE_TTY;
}
int sys_fstat(int file, struct stat *st)
{
    if (is_fd_bad(file))
    {
        return -1;
    }
    file_t *p_file = task_file(file);
    if (!p_file)
    {
        log_printf("sys_fstat: file not opended. %d", file);
        return -1;
    }
    kernel_memset(st, 0, sizeof(struct stat));
    fs_protect(p_file->fs);
    int err = p_file->fs->op->stat(p_file, st);
    fs_unprotect(p_file->fs);
    return err;
}
int sys_dup(int file)
{
    // if((file<0) || (file>=TASK_OFILE_NR)){
    if (is_fd_bad(file))
    {
        return -1;
    }
    file_t *p_file = task_file(file);
    if (!p_file)
    {
        log_printf("sys_dup: file not opened");
        return -1;
    }
    int fd = task_alloc_fd(p_file);
    if (fd >= 0)
    {
        // p_file->ref++;
        file_inc_ref(p_file);
        return fd;
    }
    log_printf("sys_dup: no task file available");

    return -1;
}
int sys_opendir(const char *path, DIR *dir)
{
    fs_protect(root_fs);
    int err = root_fs->op->opendir(root_fs, path, dir);
    fs_unprotect(root_fs);
    return err;
}
int sys_readdir(DIR *dir, struct dirent *dirent)
{
    fs_protect(root_fs);
    int err = root_fs->op->readdir(root_fs, dir, dirent);
    fs_unprotect(root_fs);
    return err;
}
int sys_closedir(DIR *dir)
{
    fs_protect(root_fs);
    int err = root_fs->op->closedir(root_fs, dir);
    fs_unprotect(root_fs);
    return err;
}
int sys_unlink(const char *path)
{
    fs_protect(root_fs);
    int err = root_fs->op->unlink(root_fs, path);
    fs_unprotect(root_fs);
    return err;
}